10. 날짜·시간 함수¶
예제 코드의 실행 결과 중
Sys.Date()·Sys.time()·Sys.timezone()처럼 "지금 이 순간"이나 "이 컴퓨터의 설정"에 의존하는 값은 이 매뉴얼을 집필한 서버 환경(시간대: UTC)을 기준으로 표시했습니다. 독자의 컴퓨터에서 직접 실행하면 실행 시점과 운영체제의 시간대·로케일(locale) 설정에 따라 다른 값이 나올 수 있습니다.
10.1 현재 날짜·시간¶
데이터를 저장한 시각을 기록으로 남기거나, 파일 이름에 오늘 날짜를 자동으로 붙이거나, "오늘부터 30일 후"처럼 지금 이 순간을 기준으로 계산을 시작해야 하는 경우가 많습니다. 이럴 때마다 날짜를 손으로 입력하면 코드를 실행하는 날짜가 바뀔 때마다 값을 고쳐야 하는 번거로움이 생깁니다.
분석 결과를 저장할 때 파일 이름에 오늘 날짜를 자동으로 넣거나, 로그에 실행 시각을 남기는 상황을 생각해 볼 수 있습니다.
paste0("결과_", Sys.Date(), ".csv")
#> [1] "결과_2026-07-20.csv"
paste0("로그 기록 시각: ", Sys.time())
#> [1] "로그 기록 시각: 2026-07-20 12:40:38"
"지금 이 순간"을 얻는 함수는 필요한 정밀도에 따라 구분해서 씁니다.
| 함수 | 반환 클래스 | 정밀도 | 주 용도 |
|---|---|---|---|
Sys.Date() |
Date |
일(day) 단위 | 날짜만 필요할 때(파일명, 보고서 작성일 등) |
Sys.time() |
POSIXct |
초(second) 단위(소수점 이하 포함) | 정확한 시각이 필요할 때(로그 타임스탬프 등) |
Sys.timezone() |
문자형 | 해당 없음 | 현재 세션에 적용된 시간대 이름 확인 |
Sys.Date()¶
Sys.Date()는 운영체제로부터 오늘 날짜를 받아와 Date 클래스 객체로 반환합니다.
unclass()로 감싸 보면 Date 객체의 실제 정체를 알 수 있습니다. 20654라는 숫자는 1970년 1월 1일을 기준(origin)으로 그날까지 지난 일수입니다. 즉 Date 클래스는 "정수에 날짜로 보이도록 출력하는 방법을 얹어 놓은 것"에 가까우며, 이 정수 표현 덕분에 뒤에서 다룰 d + 10처럼 정수를 더하고 빼는 연산이 자연스럽게 성립합니다.
Sys.time()¶
Sys.time()은 운영체제로부터 현재 시각을 받아와 POSIXct 클래스 객체로 반환합니다.
Date가 날짜(일 단위)만 다루는 것과 달리, Sys.time()이 반환하는 POSIXct는 시·분·초까지 포함하며 출력 끝에 시간대 약어(위 예시의 UTC)가 함께 표시됩니다. POSIXct·POSIXlt 두 클래스의 차이는 10.2절에서 자세히 다룹니다.
두 시점의 Sys.time() 값을 빼면 실행에 걸린 시간을 알 수 있습니다.
start <- Sys.time()
Sys.sleep(0.2) # 0.2초 대기(시연용)
end <- Sys.time()
end - start
#> Time difference of 0.2004783 secs
💡 더 알아보기:
Sys.time()과system.time()은 다른 함수입니다이름이 비슷해 자주 혼동되지만 용도가 전혀 다릅니다.
Sys.time()은 "지금이 몇 시인가"를 알려주는 시계이고,system.time()은 코드 한 덩어리(표현식)를 실행하는 데 걸린 시간을 재는 스톱워치입니다. 실행 시간만 측정하고 싶다면 위 예제처럼Sys.time()을 두 번 불러 빼는 것보다system.time()이 더 간편합니다.
사용자(user)·시스템(system)은 CPU가 실제로 일한 시간,elapsed는 사람이 체감하는 실제 경과 시간입니다. 코드 실행 시간을 측정하는 더 자세한 내용은 15장(시스템 함수)에서 다룹니다.
Sys.timezone()¶
Sys.timezone(location = TRUE)는 현재 R 세션에 적용되어 있는 시간대(timezone) 이름을 반환합니다.
location:TRUE(기본값)이면 운영체제가 알려주는 지역 기반 이름("Asia/Seoul" 같은 형식)을,FALSE이면 시스템 저수준 정보를 그대로 반환합니다.
독자의 컴퓨터가 한국 표준시로 설정되어 있다면 "Asia/Seoul"처럼 다른 값이 나오는 것이 정상입니다. Sys.getenv("TZ")가 빈 문자열("")이라는 것은 TZ 환경변수를 따로 지정하지 않고 운영체제의 기본 설정을 그대로 따르고 있다는 뜻입니다.
R이 인식할 수 있는 전체 시간대 이름 목록은 OlsonNames()로 확인할 수 있습니다. 대륙/도시 형태("Asia/Seoul", "America/New_York")로 되어 있어 "GMT+9"처럼 오프셋만으로 표기하는 것보다 일광절약시간(DST) 적용 여부까지 정확하게 처리된다는 장점이 있습니다.
head(OlsonNames(), 5)
#> [1] "Africa/Abidjan" "Africa/Accra" "Africa/Addis_Ababa"
#> [4] "Africa/Algiers" "Africa/Asmara"
length(OlsonNames())
#> [1] 597
grep("Seoul", OlsonNames(), value = TRUE)
#> [1] "Asia/Seoul"
⚠️ 주의: 클라우드 서버나 Docker 컨테이너에서 R을 실행하면 시스템 시간대가 한국이 아닌 UTC로 설정되어 있는 경우가 흔합니다. 이 상태에서 시간대를 명시하지 않고 날짜·시각을 다루면 날짜가 하루 밀리는 등의 오류가 생길 수 있으므로, 되도록
as.POSIXct(..., tz = "Asia/Seoul")처럼 시간대를 직접 지정하는 습관을 들이는 것이 안전합니다.
10.2 생성·변환¶
엑셀이나 CSV 파일에서 읽어온 날짜는 대부분 "2026-07-20"처럼 그냥 문자열(character)로 되어 있습니다. 문자열 상태로는 "이 날짜가 저 날짜보다 빠른가?", "두 날짜 사이가 며칠인가?" 같은 질문에 답할 수 없으므로, R이 날짜로 인식하는 전용 객체로 바꿔 주어야 합니다.
회원 가입일이 문자열로 저장된 데이터에서 가입일 순으로 정렬하거나 오늘까지 며칠이 지났는지 계산하려면, 먼저 문자열을 날짜 객체로 변환해야 합니다.
x <- "2026-07-20" # 문자열일 때는 대소 비교가 사전순으로만 이루어짐
class(x)
#> [1] "character"
d <- as.Date(x) # Date 객체로 변환
class(d)
#> [1] "Date"
일반화: R은 "날짜만" 다루는지 "날짜+시각"까지 다루는지에 따라 클래스를 나눕니다.
| 클래스 | 다루는 범위 | 대표 생성 함수 | 내부 표현 |
|---|---|---|---|
Date |
날짜(일 단위) | as.Date() |
1970-01-01부터 지난 일수(정수) |
POSIXct |
날짜+시각(초 단위) | as.POSIXct() |
1970-01-01 00:00:00 UTC부터 지난 초(실수) — "calendar time" |
POSIXlt |
날짜+시각(초 단위) | as.POSIXlt() |
연·월·일·시·분·초 등을 각각의 원소로 분해해 담은 리스트 — "local time list" |
POSIXct는 숫자 하나로 저장되어 있어 메모리를 적게 쓰고 벡터·데이터프레임에 담기 좋으므로 실무에서 훨씬 많이 쓰이고, POSIXlt는 "연도만 꺼내기"처럼 특정 구성요소에 바로 접근해야 할 때 유용합니다.
as.Date()¶
as.Date(x, format, tryFormats = c("%Y-%m-%d", "%Y/%m/%d"), optional = FALSE, ...)는 문자열이나 숫자 x를 Date 클래스 객체로 변환합니다.
x: 변환할 문자열 또는 숫자(정수) 벡터.format: x가 어떤 형식의 문자열인지 알려주는 서식 코드(예:"%Y%m%d"). 서식 코드의 전체 목록은 10.3절 표에서 다룹니다. 지정하지 않으면tryFormats에 있는 형식들을 순서대로 시도합니다.tryFormats:format을 지정하지 않았을 때 순서대로 시도해 볼 서식 후보들입니다. 기본값이"%Y-%m-%d"와"%Y/%m/%d"이기 때문에, 이 두 형식 중 하나라면format없이도 바로 변환됩니다.optional:TRUE로 지정하면 변환에 실패했을 때 오류 대신NA를 반환합니다(기본값FALSE는 오류를 냅니다)....: 숫자를 변환할 때 기준일을 지정하는origin인자가 여기에 해당합니다.
as.Date("2026-07-20") # 기본 서식(%Y-%m-%d)
#> [1] "2026-07-20"
as.Date("2026/07/20") # 기본 서식(%Y/%m/%d)도 지원
#> [1] "2026-07-20"
as.Date("20260720", format = "%Y%m%d") # 구분자가 없는 형식
#> [1] "2026-07-20"
as.Date("07-20-2026", format = "%m-%d-%Y") # 월-일-연 순서(미국식)
#> [1] "2026-07-20"
as.Date("2026년 7월 20일", format = "%Y년 %m월 %d일") # 한글이 섞인 형식도 가능
#> [1] "2026-07-20"
x에 숫자를 넣으면 origin(기준일)으로부터 며칠이 지난 날짜인지로 해석합니다. 기준일을 무엇으로 잡느냐에 따라 같은 숫자도 다른 날짜가 됩니다.
as.Date(0, origin = "1970-01-01") # R/Unix의 기준일
#> [1] "1970-01-01"
as.Date(20654, origin = "1970-01-01")
#> [1] "2026-07-20"
as.Date(44000, origin = "1899-12-30") # 엑셀(Windows)의 날짜 기준일
#> [1] "2020-06-18"
세 번째 예제처럼 엑셀에서 내보낸 데이터에 날짜가 정수(시리얼 번호)로 남아 있는 경우가 종종 있는데, 엑셀(Windows 버전)의 기준일인 "1899-12-30"을 origin으로 지정하면 원래 날짜로 복원할 수 있습니다.
format을 지정하지 않은 상태에서 tryFormats의 두 형식 중 어느 것과도 맞지 않는 문자열을 넣으면 오류가 발생합니다.
tryCatch(
as.Date("2026.07.20"),
error = function(e) cat("에러:", conditionMessage(e), "\n")
)
as.Date("2026.07.20", format = "%Y.%m.%d") # format을 명시하면 해결
#> 에러: 문자열이 표준서식을 따르지 않습니다
#> [1] "2026-07-20"
as.POSIXct() / as.POSIXlt()¶
as.POSIXct(x, tz = "", format, ...)와 as.POSIXlt(x, tz = "", format, ...)는 문자열이나 숫자 x를 시각까지 포함한 날짜·시각 객체로 변환합니다. 인자 구성은 거의 같지만 반환하는 내부 표현이 다릅니다.
x: 변환할 문자열, 숫자, 또는Date객체.tz: 시간대. 빈 문자열(기본값)이면 시스템의 기본 시간대를 따릅니다."Asia/Seoul"처럼OlsonNames()에 있는 이름을 지정하는 것이 안전합니다.format:as.Date()와 마찬가지로 x의 형식을 알려주는 서식 코드입니다. 생략하면"%Y-%m-%d %H:%M:%S"등 흔히 쓰는 형식들을 자동으로 시도합니다.
as.POSIXct()는 결과를 숫자 하나(1970-01-01 00:00:00 UTC부터 지난 초)로 저장하고, as.POSIXlt()는 연·월·일·시·분·초 등을 각각 이름 붙은 원소로 나누어 리스트 형태로 저장합니다. unclass()로 두 표현을 비교하면 이 차이가 분명하게 드러납니다.
pct <- as.POSIXct("2026-07-20 14:30:00", tz = "Asia/Seoul")
pct
#> [1] "2026-07-20 14:30:00 KST"
unclass(pct)
#> [1] 1784525400
#> attr(,"tzone")
#> [1] "Asia/Seoul"
plt <- as.POSIXlt("2026-07-20 14:30:00", tz = "Asia/Seoul")
unclass(plt)
#> $sec
#> [1] 0
#>
#> $min
#> [1] 30
#>
#> $hour
#> [1] 14
#>
#> $mday
#> [1] 20
#>
#> $mon
#> [1] 6
#>
#> $year
#> [1] 126
#>
#> $wday
#> [1] 1
#>
#> $yday
#> [1] 200
#>
#> $isdst
#> [1] 0
#>
#> $zone
#> [1] "KST"
#>
#> $gmtoff
#> [1] NA
#>
#> attr(,"tzone")
#> [1] "Asia/Seoul"
#> attr(,"balanced")
#> [1] TRUE
POSIXlt는 $ 기호로 원하는 구성요소에 바로 접근할 수 있다는 것이 장점입니다.
⚠️ 주의:
POSIXlt의$year와$mon은 흔히 기대하는 값 그대로가 아닙니다.$year는 1900년을 기준으로 뺀 값이고(126 → 2026년),$mon은 0부터 시작하는 월입니다(6 → 7월). C 언어의 날짜 구조체(struct tm) 관례를 그대로 물려받았기 때문인데, 실제 연도·월 숫자가 필요하면 다음과 같이 보정해야 합니다.이런 함정 때문에 실무에서는
$year·$mon을 직접 꺼내 쓰기보다 10.3절의format()이나weekdays()처럼 이미 보정이 끝난 값을 반환하는 함수를 쓰는 것이 더 안전합니다.
같은 시각도 tz를 다르게 지정해서 표시하면 다른 시각으로 보입니다. 실제로 가리키는 순간은 동일하며 "그 순간을 어느 지역의 벽시계 기준으로 읽을 것인가"만 달라진 것입니다.
pct # KST(한국 표준시) 기준
#> [1] "2026-07-20 14:30:00 KST"
format(pct, tz = "America/New_York") # 같은 순간을 뉴욕 기준으로
#> [1] "2026-07-20 01:30:00"
format(pct, tz = "UTC") # 같은 순간을 UTC 기준으로
#> [1] "2026-07-20 05:30:00"
strptime()¶
strptime(x, format, tz = "")는 문자열 x를 지정한 format(서식 코드)에 맞추어 파싱(해석)해 POSIXlt 객체로 반환합니다.
x: 파싱할 문자열.format: x의 형식을 알려주는 서식 코드.as.POSIXct()·as.POSIXlt()와 달리 생략할 수 없습니다.tz:as.POSIXct()와 동일한 역할입니다.
사실 as.POSIXct(x, format = ...)나 as.POSIXlt(x, format = ...)를 문자열에 대해 호출하면 내부적으로 이 strptime()을 이용해 파싱합니다. 즉 strptime()은 날짜·시각 파싱의 "엔진"에 해당하는 함수이며, as.POSIXct()·as.POSIXlt()가 자동으로 시도하는 몇 가지 표준 형식("%Y-%m-%d %H:%M:%S" 등)으로 해결되지 않는 독특한 형식의 문자열을 다룰 때 직접 사용합니다.
strptime("2026/07/20 14:30:00", format = "%Y/%m/%d %H:%M:%S", tz = "Asia/Seoul")
#> [1] "2026-07-20 14:30:00 KST"
old <- Sys.getlocale("LC_TIME")
Sys.setlocale("LC_TIME", "English")
#> [1] "English_United States.1252"
strptime("20-Jul-2026", format = "%d-%b-%Y") # 월 이름 약어(%b) 포함 형식
#> [1] "2026-07-20 KST"
Sys.setlocale("LC_TIME", old)
#> [1] "Korean_Korea.utf8"
%b는 로캘에 따라 해석되는 월 이름의 약어입니다. 따라서 영어 월 약어(Jan, Feb, Jul 등)를 사용하는 경우 Windows의 R에서 한국어 로캘이 설정되어 있으면 %b가 Jul을 영어 월 이름으로 해석하지 못할 수 있습니다. 그러면 날짜 변환에 실패하여 NA가 반환됩니다. 가장 간단한 해결 방법은 영어 로캘로 잠시 변경한 후 변환하는 것입니다.
strptime()이 문자열을 날짜·시각 객체로 "해석(parse)"하는 함수라면, 10.3절에서 다룰 format()·strftime()은 반대로 날짜·시각 객체를 문자열로 "표시(format)"하는 함수입니다. 이름의 p(parse)와 f(format)로 두 함수의 방향을 기억하면 헷갈리지 않습니다.
ISOdate() / ISOdatetime()¶
ISOdate(year, month, day, hour = 12, min = 0, sec = 0, tz = "GMT")와 ISOdatetime(year, month, day, hour, min, sec, tz = "")는 연·월·일·시·분·초를 각각 숫자로 따로 받아 POSIXct 객체를 조립합니다. 8.2절(seq())에서 규칙적인 날짜열을 만들 때 이미 사용해 본 함수이며, 여기서는 정식으로 다룹니다.
year,month,day: 연·월·일 숫자(필수).hour,min,sec: 시·분·초 숫자.ISOdate()는 기본값(hour = 12)이 있어 생략할 수 있지만,ISOdatetime()은 기본값이 없어 반드시 지정해야 합니다.tz: 시간대.ISOdate()의 기본값은"GMT",ISOdatetime()의 기본값은 시스템 기본 시간대입니다.
ISOdate(2026, 7, 20) # hour를 생략하면 정오(12시)로 채워짐
#> [1] "2026-07-20 12:00:00 GMT"
ISOdate(2026, 7, 20, hour = 15, tz = "Asia/Seoul")
#> [1] "2026-07-20 15:00:00 KST"
class(ISOdate(2026, 7, 20))
#> [1] "POSIXct" "POSIXt"
ISOdatetime(2026, 7, 20, 14, 30, 0, tz = "Asia/Seoul")
#> [1] "2026-07-20 14:30:00 KST"
ISOdate()의 시각 기본값이 자정(00시)이 아니라 정오(12시)로 되어 있는 것은 의도적인 설계입니다. 자정 근처는 일광절약시간(DST) 전환이 겹쳐 날짜가 하루 밀리는 사고가 생기기 쉬운 반면, 정오는 그런 경계에서 가장 멀리 떨어져 있어 "날짜만 필요하고 정확한 시각은 중요하지 않은" 상황에 안전한 기본값이기 때문입니다.
이 두 함수는 데이터프레임에 연·월·일이 각각 별도의 열로 흩어져 있는 실무 데이터를 하나의 날짜 열로 합칠 때 특히 유용합니다.
df <- data.frame(y = c(2026, 2026, 2025), m = c(1, 7, 12), d = c(15, 20, 25))
as.Date(ISOdate(df$y, df$m, df$d))
#> [1] "2026-01-15" "2026-07-20" "2025-12-25"
10.3 서식 지정¶
날짜·시각 객체는 R 내부에 정수나 초 단위 숫자로 저장되어 있지만, 보고서나 그래프에 넣을 때는 "2026년 7월 20일 월요일"처럼 사람이 읽기 좋은 형태로 바꿔야 합니다. 반대로 아주 독특한 형식의 문자열은 서식 코드를 정확히 지정해야만 제대로 변환됩니다.
보고서 제목에 오늘 날짜를 "2026년 07월 20일" 형식으로 넣거나, 그래프의 x축에 "1월", "2월"처럼 월 이름만 표시하는 상황을 생각해 볼 수 있습니다.
format()은 Date·POSIXct·POSIXlt 객체 모두에 대해 아래와 같은 서식 코드(format code)를 조합해 원하는 문자열을 만들어 냅니다. 실무에서 자주 쓰는 코드는 다음과 같습니다.
| 코드 | 의미 | 예시(2026-07-20 14:05:09 기준) |
|---|---|---|
%Y |
연도(4자리) | 2026 |
%y |
연도(2자리) | 26 |
%m |
월(2자리, 01~12) | 07 |
%B |
월 이름(전체, 로케일에 따라 다름) | July |
%b |
월 이름(약어, 로케일에 따라 다름) | Jul |
%d |
일(2자리, 01~31) | 20 |
%e |
일(공백으로 자리 채움, 1~31) | 20 |
%A |
요일 이름(전체, 로케일에 따라 다름) | Monday |
%a |
요일 이름(약어, 로케일에 따라 다름) | Mon |
%u |
요일 번호(1=월요일 ... 7=일요일, 로케일 무관) | 1 |
%w |
요일 번호(0=일요일 ... 6=토요일, 로케일 무관) | 1 |
%j |
그 해의 몇 번째 날(001~366) | 201 |
%H |
시(24시간제, 00~23) | 14 |
%I |
시(12시간제, 01~12) | 02 |
%M |
분(00~59) | 05 |
%S |
초(00~59) | 09 |
%p |
오전/오후 표시(AM/PM, 로케일에 따라 다름) | PM |
%Z |
시간대 약어 | KST |
%z |
UTC 대비 시차 | +0900 |
format() / format.Date() / format.POSIXct()¶
format(x, format = "", ...)는 날짜·시각 객체 x를 지정한 서식의 문자열로 바꿉니다. format은 제네릭(generic) 함수라서, x의 클래스가 Date인지 POSIXct인지에 따라 내부적으로 format.Date() 또는 format.POSIXct()가 자동으로 호출됩니다(이런 클래스별 자동 분기의 원리는 11.1절 class()·19장 S3 클래스에서 다룹니다).
x: 서식을 지정할Date·POSIXct·POSIXlt객체.format: 위 표의 서식 코드를 조합한 문자열. 생략하면 각 클래스의 표준 형식(Date는"%Y-%m-%d")으로 표시됩니다.tz,usetz:POSIXct·POSIXlt에만 해당하며, 결과를 표시할 시간대와 시간대 약어를 함께 표시할지(usetz) 정합니다.
d <- as.Date("2026-07-20")
t <- as.POSIXct("2026-07-20 14:05:09", tz = "Asia/Seoul")
format(d, "%Y-%m-%d")
#> [1] "2026-07-20"
format(d, "%Y년 %m월 %d일")
#> [1] "2026년 07월 20일"
format(d, "%y/%m/%d")
#> [1] "26/07/20"
format(d, "%B %d, %Y")
#> [1] "7월 20, 2026"
format(d, "%A")
#> [1] "월요일"
format(d, "%j")
#> [1] "201"
format(t, "%Y-%m-%d %H:%M:%S")
#> [1] "2026-07-20 14:05:09"
format(t, "%I:%M %p")
#> [1] "02:05 오후"
format(t, "%Y-%m-%d %H:%M:%S %Z")
#> [1] "2026-07-20 14:05:09 KST"
📌 참고: 한국어로 요일·월 이름 출력하기
%A·%B·%p처럼 이름을 그대로 풀어 쓰는 서식 코드는 R 세션의LC_TIME로케일 설정을 따릅니다. 위 예제처럼 별도 설정 없이 실행하면 R의 기본값인"C"로케일을 따라 영어로 표시됩니다. 한국어로 표시하려면Sys.setlocale()로 로케일을 명시적으로 바꿔 주어야 합니다.Sys.setlocale("LC_TIME", "ko_KR.UTF-8") # 성공하면 지정한 로케일 이름을 반환 format(d, "%A") format(d, "%Y년 %m월 %d일 %A") #> [1] "ko_KR.UTF-8" #> [1] "월요일" #> [1] "2026년 07월 20일 월요일"위 예제는 macOS·Linux 기준이며, Windows에서는 로케일 이름을
"Korean_Korea.949"처럼 다르게 표기합니다. 지정한 로케일이 운영체제에 설치되어 있지 않으면 경고와 함께 빈 문자열("")을 반환하므로, 반환값을 보면 성공 여부를 바로 확인할 수 있습니다. 이 설정은%A·%B·%p처럼 이름을 풀어 쓰는 서식 코드에만 영향을 주며,%Y·%m·%d처럼 숫자로만 이루어진 코드에는 영향을 주지 않습니다.
strftime()¶
strftime(x, format, tz = "", usetz = FALSE, ...)는 format()의 별칭(alias)에 가까운 함수로, POSIX 계열 운영체제의 같은 이름 함수와 호환되도록 base R에 함께 포함되어 있습니다. 인자 구성과 서식 코드는 format()과 동일하며, 실제로 두 함수는 같은 결과를 돌려줍니다.
strftime(t, "%Y-%m-%d")
#> [1] "2026-07-20"
identical(format(t, "%Y-%m-%d"), strftime(t, "%Y-%m-%d"))
#> [1] TRUE
strptime()이 문자열을 객체로 "해석"하고 strftime()이 객체를 문자열로 "표시"하는 대칭 관계라는 점에서 이름을 기억해 두면 편리합니다. 다만 실무에서는 Date·POSIXct·POSIXlt 어디에나 똑같이 쓸 수 있는 format()을 strftime()보다 더 널리 사용합니다.
weekdays() / months() / quarters()¶
weekdays(x, abbreviate = FALSE), months(x, abbreviate = FALSE), quarters(x, ...)는 각각 요일·월·분기 이름만 뽑아내는 편의 함수로, format(x, "%A")처럼 서식 코드를 직접 조합하는 수고를 덜어 줍니다.
x:Date·POSIXct·POSIXlt객체.abbreviate:TRUE이면 약어("Mon","Jul"등)로,FALSE(기본값)이면 전체 이름으로 반환합니다(quarters()에는 이 인자가 없습니다).
weekdays(d)
#> [1] "월요일"
weekdays(d, abbreviate = TRUE)
#> [1] "월
months(d)
#> [1] "7월"
quarters(d)
#> [1] "Q3"
날짜 벡터에 적용하면 데이터프레임의 한 열로 바로 활용할 수 있습니다.
dates_vec <- as.Date("2026-01-01") + c(0, 90, 180, 270)
data.frame(날짜 = dates_vec, 요일 = weekdays(dates_vec), 분기 = quarters(dates_vec))
#> 날짜 요일 분기
#> 1 2026-01-01 목요일 Q1
#> 2 2026-04-01 수요일 Q2
#> 3 2026-06-30 화요일 Q2
#> 4 2026-09-28 월요일 Q3
⚠️ 주의:
weekdays()가 반환하는 문자열은%A·%a와 마찬가지로 로케일에 따라 달라집니다. 예를 들어 "주말(토·일)만 걸러내자"는 코드를weekdays(x, abbreviate = TRUE) %in% c("Sat", "Sun")처럼 짜면, 로케일이"C"(영어)일 때는 잘 동작하지만 앞서 소개한 것처럼 로케일을 한국어로 바꿔 둔 세션에서는"토"·"일"이 반환되어"Sat"·"Sun"과 매칭되지 않고 조용히(오류 없이) 틀린 결과를 냅니다. 이런 문제를 피하려면 이름 대신 로케일에 영향받지 않는 숫자 코드%u(1=월요일 ~ 7=일요일)를 사용하는 것이 안전합니다. 이 예제는 10.4절에서 실제 데이터로 다시 확인합니다.
10.4 연산¶
날짜·시각 데이터를 실제 분석에 활용하려면 두 날짜 사이의 간격을 계산하거나, 일정한 간격의 날짜 축을 만들거나, 개별 날짜들을 특정 기간 단위(월·분기 등)로 묶어 집계해야 하는 경우가 많습니다.
회원 가입일과 오늘 날짜로 가입 후 경과일수를 구하거나, 여러 날짜에 흩어진 매출 기록을 월 단위로 묶어 집계하는 상황을 생각해 볼 수 있습니다.
날짜·시각 연산은 목적에 따라 다음과 같이 정리할 수 있습니다.
| 상황 | 사용하는 함수·연산자 |
|---|---|
| 날짜·시각에 일수·초수를 더하고 뺄 때 | +, - |
| 두 날짜·시각의 차이를 원하는 단위로 구할 때 | difftime() |
| 일정한 간격의 날짜열을 만들 때 | seq()(8.2절에서 이미 다룸) |
| 날짜를 구간(일·주·월·분기·연) 단위로 묶을 때 | cut()(cut.Date()) |
날짜·시각의 산술 연산 (+, -)¶
Date와 POSIXct 객체는 10.2절에서 살펴본 것처럼 내부적으로 숫자(정수 또는 초)이기 때문에, 숫자를 더하고 빼는 일반적인 산술 연산자를 그대로 쓸 수 있습니다. 다만 더해지는 숫자의 단위가 클래스마다 다르다는 점에 유의해야 합니다 — Date는 일(day) 단위, POSIXct는 초(second) 단위입니다.
d1 <- as.Date("2026-03-01")
d1 + 10 # Date + 정수: 정수만큼 며칠 뒤
#> [1] "2026-03-11"
d1 - 5 # Date - 정수: 정수만큼 며칠 전
#> [1] "2026-02-24"
t1 <- as.POSIXct("2026-07-20 09:00:00", tz = "Asia/Seoul")
t1 + 3600 # POSIXct + 정수: 정수(초)만큼 뒤
#> [1] "2026-07-20 10:00:00 KST"
t1 + 60 * 60 * 24 # 하루(60초 x 60분 x 24시간) 뒤
#> [1] "2026-07-21 09:00:00 KST"
같은 클래스의 두 날짜·시각을 빼면 숫자가 아니라 difftime 클래스 객체가 반환됩니다.
difftime()¶
difftime(time1, time2, tz, units = c("auto", "secs", "mins", "hours", "days", "weeks"))는 time1 - time2, 즉 두 날짜·시각의 차이를 원하는 단위로 명시해서 구합니다.
time1,time2: 비교할 두Date또는POSIXct(POSIXlt) 객체.tz: 서로 다른 시간대의POSIXct를 비교할 때 기준으로 삼을 시간대(생략하면 자동으로 처리됩니다).units: 결과를 표시할 단위."auto"(기본값)는 두 시점의 간격 크기에 맞춰 적절한 단위(초·분·시간·일·주)를 자동으로 고릅니다.
바로 위에서 본 d2 - d1도 사실 difftime(d2, d1)을 간편하게 쓴 것과 같습니다. - 연산자는 units를 지정할 수 없다는 점이 유일한 차이이며, 단위를 직접 고르고 싶을 때는 difftime()을 명시적으로 호출해야 합니다.
difftime(d2, d1) # units = "auto": 날짜 차이는 "days"로 자동 선택
#> Time difference of 141 days
difftime(d2, d1, units = "weeks")
#> Time difference of 20.14286 weeks
difftime(d2, d1, units = "hours")
#> Time difference of 3384 hours
as.numeric(difftime(d2, d1, units = "days")) # 숫자만 필요하면 as.numeric()으로 벗겨냄
#> [1] 141
시각(POSIXct)끼리 비교하면 자동으로 더 세밀한 단위가 선택됩니다.
t2 <- as.POSIXct("2026-07-20 18:30:00", tz = "Asia/Seoul")
difftime(t2, t1, units = "mins")
#> Time difference of 570 mins
difftime(as.POSIXct("2026-07-22 09:00:00", tz = "Asia/Seoul"), t1) # units = "auto"
#> Time difference of 2 days
seq.Date() — 날짜 전용 활용¶
seq()가 Date·POSIXct 객체에 대해서도 동작한다는 것은 8.2절에서 이미 자세히 다뤘습니다(연·월·분기 간격 생성, "DSTday" 등). 여기서는 실무에서 자주 필요한 두 가지 활용을 추가로 살펴봅니다.
첫 번째는 영업일(평일)만 남기는 날짜열입니다. 앞서 10.3절에서 경고한 대로, 요일 이름으로 필터링하면 로케일에 따라 결과가 달라질 수 있으므로 로케일에 영향받지 않는 %u 코드(1=월요일 ~ 7=일요일)를 사용하는 것이 안전합니다.
all_days <- seq(as.Date("2026-07-20"), as.Date("2026-07-26"), by = "day")
is_weekday <- !format(all_days, "%u") %in% c("6", "7") # 6=토, 7=일
all_days[is_weekday]
#> [1] "2026-07-20" "2026-07-21" "2026-07-22" "2026-07-23" "2026-07-24"
두 번째는 월말(그 달의 마지막 날) 날짜열을 만드는 것입니다. R에는 "이번 달의 마지막 날"을 곧바로 계산해 주는 함수가 없지만, "다음 달 1일에서 하루를 빼면 이번 달의 마지막 날"이라는 규칙을 이용하면 간단히 구할 수 있습니다.
month_starts <- seq(as.Date("2026-01-01"), by = "month", length.out = 6)
month_ends <- seq(as.Date("2026-02-01"), by = "month", length.out = 6) - 1
data.frame(시작일 = month_starts, 마지막날 = month_ends)
#> 시작일 마지막날
#> 1 2026-01-01 2026-01-31
#> 2 2026-02-01 2026-02-28
#> 3 2026-03-01 2026-03-31
#> 4 2026-04-01 2026-04-30
#> 5 2026-05-01 2026-05-31
#> 6 2026-06-01 2026-06-30
cut.Date()¶
날짜 벡터를 cut(x, breaks, labels = NULL, start.on.monday = TRUE, right = FALSE, ...)에 넣으면 일정한 기간 단위로 묶어 팩터(factor)로 반환하는 cut.Date()가 자동으로 호출됩니다. 흩어진 날짜들을 "월별"·"주별" 그룹으로 나누어 tapply()나 aggregate()와 함께 집계할 때 특히 유용합니다.
x: 묶을 대상Date벡터.breaks: 묶는 기준."day"·"week"·"month"·"quarter"·"year"같은 문자열이나, 며칠 간격으로 끊을지를 나타내는 숫자(예:7)를 지정합니다.labels: 각 구간에 붙일 이름. 생략하면 구간의 시작 날짜가 그대로 이름이 됩니다.start.on.monday:breaks = "week"일 때 한 주의 시작을 월요일(TRUE, 기본값)로 볼지 일요일로 볼지 정합니다.
set.seed(1)
dates <- as.Date("2026-01-01") + sort(sample(0:200, 15)) # 무작위 날짜 15개
g <- cut(dates, breaks = "month")
table(g)
#> g
#> 2026-01-01 2026-02-01 2026-03-01 2026-04-01 2026-05-01 2026-06-01 2026-07-01
#> 3 2 4 1 1 2 2
cut()으로 나눈 결과를 table()에 바로 넣으면 월별 데이터 개수를, aggregate()나 tapply()의 그룹 기준(by, INDEX)으로 쓰면 월별 평균·합계 같은 요약 통계를 손쉽게 구할 수 있습니다.
💡 더 알아보기:
round()·trunc()와julian()
POSIXct·POSIXlt객체는round(x, units)·trunc(x, units)로 시·분 단위 이하를 반올림하거나 잘라낼 수 있습니다(units에는"secs"·"mins"·"hours"·"days"·"months"·"years"를 지정합니다).t3 <- as.POSIXct("2026-07-20 14:47:32", tz = "Asia/Seoul") round(t3, "hours") # 47분 32초 -> 가장 가까운 정시로 반올림 trunc(t3, "hours") # 47분 32초 -> 47분 32초를 버리고 14시로 #> [1] "2026-07-20 15:00:00 KST" #> [1] "2026-07-20 14:00:00 KST"
Date객체에도round()·trunc()가 정의되어 있지만"days"단위(사실상 원래 값 그대로)만 안정적으로 동작하며,"months"·"years"단위는 버전에 따라 오류가 날 수 있으므로 권장하지 않습니다. 날짜를 월·분기·연 단위로 묶는 작업은 바로 위에서 다룬cut.Date()나,format(x, "%Y-%m-01")처럼 그 달의 1일로 직접 맞추는 방법을 쓰는 것이 더 안전합니다.
julian(x, origin = as.Date("1970-01-01"), ...)는 기준일(origin)로부터 며칠이 지났는지를 구합니다.as.numeric(x)와 비슷해 보이지만, 결과에origin속성이 함께 붙어 나와 "무엇을 기준으로 센 날수인지"를 결과 값만 보고도 알 수 있다는 차이가 있습니다.